package authx

import (
	"context"
	"errors"
	"github.com/golang-jwt/jwt"
	"net/http"
)

const Identify = "imooc.com"

// JWTValidator 封装JWT验证器对象
type JWTValidator struct {
	secretKey []byte
}

// NewJWTValidator 创建一个新的JWT验证器实例
func NewJWTValidator(secretKey string) *JWTValidator {
	return &JWTValidator{
		secretKey: []byte(secretKey),
	}
}

// Parse 解析请求中的JWT token
func (v *JWTValidator) Parse(r *http.Request) (*http.Request, error) {
	// 从请求头中获取JWT token
	tokenString := r.Header.Get("Authorization")
	if tokenString == "" {
		return r, errors.New("token required")
	}
	// 去除 "Bearer " 前缀
	if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
		tokenString = tokenString[7:]
	}

	// 解析JWT token
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// 验证签名方法
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return v.secretKey, nil
	})

	if err != nil {
		return r, err
	}

	// 检查 token 是否有效
	if !token.Valid {
		return r, jwt.ErrSignatureInvalid
	}

	// 尝试将 Claims 转换为 jwt.MapClaims 类型
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return r, jwt.ErrInvalidKeyType
	}

	// 过滤JWT的标准声明
	filteredClaims := filterStandardClaims(claims)

	// 将自定义信息写入请求上下文
	ctx := r.Context()
	for key, value := range filteredClaims {
		ctx = context.WithValue(ctx, key, value)
	}
	r = r.WithContext(ctx)

	return r, nil
}

// filterStandardClaims 过滤JWT的标准声明
func filterStandardClaims(claims jwt.MapClaims) map[string]interface{} {
	standardClaims := map[string]bool{
		"exp": true,
		"iat": true,
		"nbf": true,
		"iss": true,
		"sub": true,
		"aud": true,
		"jti": true,
	}
	filteredClaims := make(map[string]interface{})
	for key, value := range claims {
		if !standardClaims[key] {
			filteredClaims[key] = value
		}
	}
	return filteredClaims
}

func GetJwtToken(secretKey string, iat, seconds int64, uid int64) (string, error) {
	claims := make(jwt.MapClaims)
	claims["exp"] = iat + seconds
	claims["iat"] = iat
	claims[Identify] = uid
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = claims
	return token.SignedString([]byte(secretKey))
}

func GetUId(ctx context.Context) int {
	var uid int
	if jsonUid, ok := ctx.Value(Identify).(float64); ok {
		uid = int(jsonUid)
	}
	return uid
}
